mov ax,cs  
mov ss,ax
mov sp,0x7c00 ; 初始化栈段，栈段是从0x7c00往下的部分。代码段往上，刚好从0x7c00分开

mov ax,[cs:gdt_base+0x7c00] ; 存放 GDT 的地方，是一个32位的地址。在实模式下，需要手动计算段地址和偏移地址
mov dx,[cs:gdt_base+0x7c00+2]
mov cx,16
div cx
; 下面要写 GDT，所以将段地址和偏移地址设置好先
mov ds,ax
mov bx,dx

mov dword [bx],0x00000000     ; GDT必须以空描述符开头
mov dword [bx+0x04],0x00000000

mov dword [bx+0x08],0x7c0001ff   ; 注册代码段
mov dword [bx+0x0c],0x00409800

mov dword [bx+0x10],0x8000ffff   ; 注册数据段
mov dword [bx+0x14],0x0040920b

mov dword [bx+0x18],0x00007a00   ; 栈段描述符
mov dword [bx+0x1c],0x00409600

; 初始化GDTR
mov word [cs:gdt_size+0x7c00],31   ;GDT的界限=total bytes-1

lgdt [cs:gdt_size+0x7c00]  ; load GDT

in al,0x92
or al,0000_0010B
out 0x92,al     ;通过南桥芯片ICH打开A20地址线

cli   ;保护模式下中断机制与实模式不同，没有建立新的中断机制之前，可以先禁止中断

mov eax,cr0
or eax,1   ; 将cr0的0位设为1，标志着进入保护模式
mov cr0,eax

; 从这里开始进入保护模式
jmp dword 0x0008:flush   ; 这里的0008是段选择子

[bits 32]

flush:
    mov cx,0000_0000_000_10_000B  ;加载数据段选择子
    mov ds,cx

    mov byte [0x00],'P'
    mov byte [0x02],'r'
    mov byte [0x04],'o'
    mov byte [0x06],'t'
    mov byte [0x08],'e'
    mov byte [0x0a],'c'
    mov byte [0x0c],'t'
    mov byte [0x0e],'e'
    mov byte [0x10],'d'
    mov byte [0x12],' '
    mov byte [0x14],'m'
    mov byte [0x16],'o'
    mov byte [0x18],'d'
    mov byte [0x1a],'e'
    mov byte [0x1c],' '
    mov byte [0x1e],'O'
    mov byte [0x20],'K'

    mov cx,0000_0000_00_11_000B
    mov ss,cx
    mov esp,0x7c00   ;栈指针初始地址0x7c00

    mov ebp,esp
    push byte '!'

    sub ebp,4
    cmp ebp,esp
    jnz ghalt
    pop eax
    mov [0x22],al

ghalt: 
    hlt

gdt_size: dw 0     ; gdt 的大小，指条数
gdt_base: dd 0x00007e00  ; gdt 的基地址

times 510-($-$$) db 0
db 0x55,0xaa
